package frame.laxcen.loop;

import frame.util.ByteUtil;
import frame.util.Ref;

import java.util.Arrays;
import java.util.HashMap;
import java.util.function.Consumer;
import java.util.function.Function;

public abstract class BaseLoopReader {

    private int position;

    private byte[] frameBytes;

    private HashMap<String, String> result;

    private void reset(byte[] bytes) {

        position = 0;

        frameBytes = bytes;

        result = new HashMap<>();
    }

    protected byte eat() {

        return frameBytes[position++];
    }

    protected byte[] eat(int n) {

        byte[] tmp = Arrays.copyOfRange(frameBytes, position, position + n);

        position += n;

        return tmp;
    }

    protected boolean sniff(byte pid) {

        return hasNext() && frameBytes[position] == pid && position++ >= -1;
    }

    protected boolean hasNext() {

        return position < frameBytes.length;
    }

    protected void readU8(String key, Function<byte[], String> function) {

        result.put(key, function.apply(eat(1)));
    }

    protected void readU8n(String key, int n, Function<byte[], String> function) {

        result.put(key, function.apply(eat(n)));
    }

    protected void readU8s(String key, Function<byte[], String> function) {

        readU8n(key, ByteUtil.bytePairToInt(eat(), eat()), function);
    }

    protected boolean readP8(String key, byte pid, Function<byte[], String> function) {

        if(!sniff(pid)) return false;

        readU8(key, function);

        return true;
    }

    protected boolean readP8n(String key, byte pid, int n, Function<byte[], String> function) {

        if(!sniff(pid)) return false;

        readU8n(key, n, function);

        return true;
    }

    protected boolean readP8s(String key, byte pid, Function<byte[], String> function) {

        if(!sniff(pid)) return false;

        readU8s(key, function);

        return true;
    }

    protected void readU16(String key, Function<byte[], String> function) {

        readU8n(key, 2, function);
    }

    protected void readU16n(String key, int n, Function<byte[], String> function) {

        readU8n(key, n + n, function);
    }

    protected void readU16s(String key, Function<byte[], String> function) {

        readU8n(key, ByteUtil.bytePairToInt(eat(), eat()) << 1, function);
    }

    protected boolean readP16(String key, byte pid, Function<byte[], String> function) {

        if(!sniff(pid)) return false;

        readU16(key, function);

        return true;
    }

    protected boolean readP16n(String key, byte pid, int n, Function<byte[], String> function) {

        if(!sniff(pid)) return false;

        readU16n(key, n, function);

        return true;
    }

    protected boolean readP16s(String key, byte pid, Function<byte[], String> function) {

        if(!sniff(pid)) return false;

        readU16s(key, function);

        return true;
    }

    protected void readU32(String key, Function<byte[], String> function) {

        readU8n(key, 4, function);
    }

    protected void readU32n(String key, int n, Function<byte[], String> function) {

        readU8n(key, n << 2, function);
    }

    protected void readU32s(String key, Function<byte[], String> function) {

        readU8n(key, ByteUtil.bytePairToInt(eat(), eat()) << 2, function);
    }

    protected boolean readP32(String key, byte pid, Function<byte[], String> function) {

        if(!sniff(pid)) return false;

        readU32(key, function);

        return true;
    }

    protected boolean readP32n(String key, byte pid, int n, Function<byte[], String> function) {

        if(!sniff(pid)) return false;

        readU32n(key, n, function);

        return true;
    }

    protected boolean readP32s(String key, byte pid, int n, Function<byte[], String> function) {

        if(!sniff(pid)) return false;

        readU32s(key, function);

        return true;
    }

    public HashMap<String, String> getResult() {

        return result;
    }

    public void decode(byte[] bytes) {

        reset(bytes);

        Ref<String> pc = Ref.of("loop-begin");

        while(hasNext() && !pc.get().equals("loop-end")) loop(pc);
    }

    protected abstract String loop(Ref<String> pc);
}
